/**
  ******************************************************************************
  * File   : usbh_msc_core.c 
  * Version: V1.2.5
  * Date   : 2020-10-16
  * Brief  : This file implements the MSC class driver functions
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/

#include "usbh_msc_core.h"
#include "usbh_msc_scsi.h"
#include "usbh_msc_bot.h"
#include "usbh_core.h"
#include "usbh_hcs.h"


/** @addtogroup USBH_LIB
  * @{
  */

/** @addtogroup USBH_CLASS
  * @{
  */

/** @addtogroup USBH_MSC_CLASS
  * @{
  */
  
/** @defgroup USBH_MSC_CORE 
  * @brief    This file includes the mass storage related functions
  * @{
  */ 


/** @defgroup USBH_MSC_CORE_Private_TypesDefinitions
  * @{
  */ 
/**
  * @}
  */ 

/** @defgroup USBH_MSC_CORE_Private_Defines
  * @{
  */ 
#define USBH_MSC_ERROR_RETRY_LIMIT 10
/**
  * @}
  */ 

/** @defgroup USBH_MSC_CORE_Private_Macros
  * @{
  */ 
/**
  * @}
  */ 


/** @defgroup USBH_MSC_CORE_Private_Variables
  * @{
  */ 
MSC_Machine_TypeDef         MSC_Machine ;

USB_Setup_Type           MSC_Setup;
uint8_t MSCErrorCount = 0;


/**
  * @}
  */ 


/** @defgroup USBH_MSC_CORE_Private_FunctionPrototypes
  * @{
  */ 

static USB_HOST_Status USBH_MSC_InterfaceInit  (USB_OTG_CORE_HANDLE *pusbdev , 
                                            void *phost);

static void USBH_MSC_InterfaceDeInit  (USB_OTG_CORE_HANDLE *pusbdev , 
                                       void *phost);

static USB_HOST_Status USBH_MSC_Handle(USB_OTG_CORE_HANDLE *pusbdev ,
                                   
                            void *phost);

static USB_HOST_Status USBH_MSC_ClassRequest(USB_OTG_CORE_HANDLE *pusbdev , 
                                         void *phost);

static USB_HOST_Status USBH_MSC_GETMaxLUN(USB_OTG_CORE_HANDLE *pusbdev,
                               USB_HOST *phost);


USB_Host_Class_cb_Type  USBH_MSC_cb = 
{
  USBH_MSC_InterfaceInit,
  USBH_MSC_InterfaceDeInit,
  USBH_MSC_ClassRequest,
  USBH_MSC_Handle,
};

void USBH_MSC_ErrorHandle(uint8_t status);

/**
  * @}
  */ 


/** @defgroup USBH_MSC_CORE_Exported_Variables
  * @{
  */ 

/**
  * @}
  */ 


/** @defgroup USBH_MSC_CORE_Private_Functions
  * @{
  */ 


/**
  * @brief  USBH_MSC_InterfaceInit 
  *         Interface initialization for MSC class.
  * @param  pusbdev: Selected device
  * @param  hdev: Selected device property
  * @retval USB_HOST_Status : Status of class request handled.
  */
static USB_HOST_Status USBH_MSC_InterfaceInit ( USB_OTG_CORE_HANDLE *pusbdev, 
                                        void *phost)
{	 
  USB_HOST *pphost = phost;
  
  if((pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_MSC_CLASS) && \
     (pphost->device_prop.Itf_Desc[0].bInterfaceProtocol == MSC_PROTOCOL))
  {
    if(pphost->device_prop.Ept_Desc[0][0].bEndpointAddress & 0x80)
    {
      MSC_Machine.MSBulkInEp = (pphost->device_prop.Ept_Desc[0][0].bEndpointAddress);
      MSC_Machine.MSBulkInEpSize  = pphost->device_prop.Ept_Desc[0][0].wMaxPacketSize;
    }
    else
    {
      MSC_Machine.MSBulkOutEp = (pphost->device_prop.Ept_Desc[0][0].bEndpointAddress);
      MSC_Machine.MSBulkOutEpSize  = pphost->device_prop.Ept_Desc[0] [0].wMaxPacketSize;      
    }
    
    if(pphost->device_prop.Ept_Desc[0][1].bEndpointAddress & 0x80)
    {
      MSC_Machine.MSBulkInEp = (pphost->device_prop.Ept_Desc[0][1].bEndpointAddress);
      MSC_Machine.MSBulkInEpSize  = pphost->device_prop.Ept_Desc[0][1].wMaxPacketSize;      
    }
    else
    {
      MSC_Machine.MSBulkOutEp = (pphost->device_prop.Ept_Desc[0][1].bEndpointAddress);
      MSC_Machine.MSBulkOutEpSize  = pphost->device_prop.Ept_Desc[0][1].wMaxPacketSize;      
    }
    
    MSC_Machine.hch_num_out = USB_Host_Alloc_Channel(pusbdev, 
                                                MSC_Machine.MSBulkOutEp);
    MSC_Machine.hch_num_in = USB_Host_Alloc_Channel(pusbdev,
                                                MSC_Machine.MSBulkInEp);  
    
    /* Open the new channels */
    USB_Host_Open_Channel  (pusbdev,
                        MSC_Machine.hch_num_out,
                        pphost->device_prop.address,
                        pphost->device_prop.speed,
                        EPT_TYPE_BULK,
                        MSC_Machine.MSBulkOutEpSize);  
    
    USB_Host_Open_Channel  (pusbdev,
                        MSC_Machine.hch_num_in,
                        pphost->device_prop.address,
                        pphost->device_prop.speed,
                        EPT_TYPE_BULK,
                        MSC_Machine.MSBulkInEpSize);    
    
  }
  
  else
  {
    if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_HUB_CLASS)
    {
      printf("Hub is not supported.\n");
    }
    
    else if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_CDCC_CLASS)
    {
      printf("Communications and CDC Control device is not supported.\n");
    }
    
    else if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_HID_CLASS)
    {
      printf("HID device is not supported.\n");
    }
    
    else if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_PRINTER_CLASS)
    {
      printf("Printer device is not supported.\n");
    }
    
    else if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_SMARTCARD_CLASS)
    {
      printf("Smart Card device is not supported.\n");
    }
    
    
    else if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_VIDEO_CLASS)
    {
      printf("Video device  is not supported.\n");
    }
    
    
    else if (pphost->device_prop.Itf_Desc[0].bInterfaceClass == USB_AVD_CLASS)
    {
      printf("Audio/Video Devices is not supported.\n");
    }
    
    else
    {
      printf ("The attached device is not supported. \n");
    }
    
    pphost->user_cb->DeviceNotSupported();
  }
  
  return USB_HOST_OK ;
 
}


/**
  * @brief  USBH_MSC_InterfaceDeInit 
  *         De-Initialize interface by freeing host channels allocated to interface
  * @param  pusbdev: Selected device
  * @param  hdev: Selected device property
  * @retval None
  */
void USBH_MSC_InterfaceDeInit ( USB_OTG_CORE_HANDLE *pusbdev,
                                void *phost)
{	
  if ( MSC_Machine.hch_num_out)
  {
    USB_OTG_HCH_Halt(pusbdev, MSC_Machine.hch_num_out);
    USB_Host_Free_Channel  (pusbdev, MSC_Machine.hch_num_out);
    MSC_Machine.hch_num_out = 0;     /* Reset the Channel as Free */
  }
   
  if ( MSC_Machine.hch_num_in)
  {
    USB_OTG_HCH_Halt(pusbdev, MSC_Machine.hch_num_in);
    USB_Host_Free_Channel  (pusbdev, MSC_Machine.hch_num_in);
    MSC_Machine.hch_num_in = 0;     /* Reset the Channel as Free */
  } 
}

/**
  * @brief  USBH_MSC_ClassRequest 
  *         This function will only initialize the MSC state machine
  * @param  pusbdev: Selected device
  * @param  hdev: Selected device property
  * @retval USB_HOST_Status : Status of class request handled.
  */

static USB_HOST_Status USBH_MSC_ClassRequest(USB_OTG_CORE_HANDLE *pusbdev , 
                                        void *phost)
{   
  
  USB_HOST_Status status = USB_HOST_OK ;
  USBH_MSC_BOTXferParam.MSCState = USBH_MSC_BOT_INIT_STATE;
  
  return status; 
}


/**
  * @brief  USBH_MSC_Handle 
  *         MSC state machine handler 
  * @param  pusbdev: Selected device
  * @param  hdev: Selected device property
  * @retval USB_HOST_Status
  */

static USB_HOST_Status USBH_MSC_Handle(USB_OTG_CORE_HANDLE *pusbdev ,
                                   void   *phost)
{
  USB_HOST *pphost = phost;
    
  USB_HOST_Status status = USB_HOST_BUSY;
  uint8_t mscStatus = USBH_MSC_BUSY;
  uint8_t appliStatus = 0;
  
 
  static uint8_t maxLunExceed = FALSE;
    
  if(USB_HCD_IsDeviceConnected(pusbdev))
  {   
    switch(USBH_MSC_BOTXferParam.MSCState)
    {
    case USBH_MSC_BOT_INIT_STATE:
      USBH_MSC_Init(pusbdev);
      USBH_MSC_BOTXferParam.MSCState = USBH_MSC_GET_MAX_LUN;  
      break;
      
      
    case USBH_MSC_GET_MAX_LUN:
      /* Issue GetMaxLUN request */
      status = USBH_MSC_GETMaxLUN(pusbdev, phost);
      
      if(status == USB_HOST_OK )
      {
        MSC_Machine.maxLun = *(MSC_Machine.buff) ;
        
        /* If device has more that one logical unit then it is not supported */
        if((MSC_Machine.maxLun > 0) && (maxLunExceed == FALSE))
        {
          maxLunExceed = TRUE;
          pphost->user_cb->DeviceNotSupported();
          
          break;
        }
        
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_TEST_UNIT_READY;
      }
      
      if(status == USB_HOST_NOT_SUPPORTED )
      {
               /* If the Command has failed, then we need to move to Next State, after
        STALL condition is cleared by Control-Transfer */
        USBH_MSC_BOTXferParam.MSCStateBkp = USBH_MSC_TEST_UNIT_READY;        
        /* a Clear Feature should be issued here */
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_CTRL_ERROR_STATE;
      }    
      break;
      
    case USBH_MSC_CTRL_ERROR_STATE:
      /* Issue Clearfeature request */
      status = USB_HOST_ClrFeature(pusbdev,
                               phost,
                               0x00,
                               pphost->Control.hch_num_out);
      if(status == USB_HOST_OK )
      {
        /* If GetMaxLun Request not support, assume Single LUN configuration */
        MSC_Machine.maxLun = 0;  
        
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_BOTXferParam.MSCStateBkp;     
      }
      break;  
      
    case USBH_MSC_TEST_UNIT_READY:
         /* Issue SCSI command TestUnitReady */ 
         mscStatus = USBH_MSC_TestUnitReady(pusbdev);

         if(mscStatus == USBH_MSC_OK )
         {
           USBH_MSC_BOTXferParam.MSCState = USBH_MSC_READ_CAPACITY10;
MSCErrorCount = 0;
        status = USB_HOST_OK;
      }
      else
      {
        USBH_MSC_ErrorHandle(mscStatus);
      } 
      break;
      
    case USBH_MSC_READ_CAPACITY10:
      /* Issue READ_CAPACITY10 SCSI command */
      mscStatus = USBH_MSC_ReadCapacity10(pusbdev);
      if(mscStatus == USBH_MSC_OK )
      {
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_MODE_SENSE6;
        MSCErrorCount = 0;
        status = USB_HOST_OK;
      }
      else
      {
        USBH_MSC_ErrorHandle(mscStatus);
      }
      break;

    case USBH_MSC_MODE_SENSE6:
      /* Issue ModeSense6 SCSI command for detecting if device is write-protected */
      mscStatus = USBH_MSC_ModeSense6(pusbdev);
      if(mscStatus == USBH_MSC_OK )
      {
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_DEFAULT_APPLI_STATE;
        MSCErrorCount = 0;
        status = USB_HOST_OK;
      }
      else
      {
        USBH_MSC_ErrorHandle(mscStatus);
      }
      break;
       
    case USBH_MSC_BOT_USB_TRANSFERS:
      /* Process the BOT state machine */
      USBH_MSC_HandleBOTXfer(pusbdev,phost);
      break;
    
    case USBH_MSC_DEFAULT_APPLI_STATE:
      /* Process Application callback for MSC */
      appliStatus = pphost->user_cb->UserApplication();
      if(appliStatus == 0)
      {
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_DEFAULT_APPLI_STATE;
      }
      else if (appliStatus == 1) 
      {
        /* De-init requested from application layer */
        status =  USB_HOST_APPLY_DEINIT;
      }
      break;
      
    case USBH_MSC_UNRECOVERED_STATE:
      
      status = USB_HOST_UNRECOVERED_ERROR;
      
      break;
    
    case USBH_MSC_REQUEST_SENSE:
      /* Issue RequestSense SCSI command for retrieving error code */
      mscStatus = USBH_MSC_RequestSense(pusbdev);
      if(mscStatus == USBH_MSC_OK )
      {
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_BOTXferParam.MSCStateBkp;
        status = USB_HOST_OK;
      }
      else
      {
        USBH_MSC_ErrorHandle(mscStatus);
      }  
      break;
      
    default:
      break; 
      
    }
  }
   return status;
}


/**
  * @brief  USBH_MSC_GETMaxLUN
  *         This request is used to reset the mass storage device and its 
  *         associated interface. This class-specific request shall ready the 
  *         device for the next CBW from the host.
  * @param  pusbdev: Selected device
  * @retval USB_HOST_Status : USB ctl xfer status
  */
static USB_HOST_Status USBH_MSC_GETMaxLUN(USB_OTG_CORE_HANDLE *pusbdev , USB_HOST *phost)
{
  phost->Control.setup.b.bmRequestType = USB_DIR_D2H | USB_REQUEST_TYPE_CLASS | \
                              USB_REQUEST_RECIPIENT_INTERFACE;
  
  phost->Control.setup.b.bRequest = USB_REQ_GET_MAX_LUN;
  phost->Control.setup.b.wValue.w = 0;
  phost->Control.setup.b.wIndex.w = 0;
  phost->Control.setup.b.wLength.w = 1;           
  
  return USB_HOST_CtrlReq(pusbdev, phost, MSC_Machine.buff , 1 ); 
}

/**
  * @brief  USBH_MSC_ErrorHandle 
  *         The function is for handling errors occurring during the MSC
  *         state machine   
  * @param  status
  * @retval None
  */

void USBH_MSC_ErrorHandle(uint8_t status)
{  
    if(status == USBH_MSC_FAIL)
    { 
      MSCErrorCount++;
      if(MSCErrorCount < USBH_MSC_ERROR_RETRY_LIMIT)
      { /* Try MSC level error recovery, Issue the request Sense to get 
        Drive error reason  */
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_REQUEST_SENSE;
        USBH_MSC_BOTXferParam.CmdStateMachine = CMD_SEND_STATE;
      }
      else
      {
        /* Error trials exceeded the limit, go to unrecovered state */
        USBH_MSC_BOTXferParam.MSCState = USBH_MSC_UNRECOVERED_STATE;
      }
    } 
    else if(status == USBH_MSC_PHASE_ERROR)
    {
      /* Phase error, Go to Unrecovered state */
      USBH_MSC_BOTXferParam.MSCState = USBH_MSC_UNRECOVERED_STATE;
    }
    else if(status == USBH_MSC_BUSY)
    {
      /*No change in state*/
    }
}

/**
  * @}
  */ 

/**
  * @}
  */ 

/**
  * @}
  */

/**
  * @}
  */ 

/**
  * @}
  */

/************************ (C) COPYRIGHT Artery Technology *****END OF FILE****/
